#include "linux/platform.h"
#include "printk.h"
#include "linux/pinctrl.h"
#include "linux/list.h"
#include "linux/io.h"
#include "linux/completion.h"
#include "linux/errno.h"
#include "linux/types.h"
#include "linux/platform_device.h"
#include "linux/spi.h"
#include "linux/init.h"
#include "linux/of.h"

#define RXDATA 0
#define TXDATA 4
#define CONREG 8
#define CONFIGREG 0xc
#define INTREG 0x10
#define DMAREG 0x14
#define STATREG 0x18
#define PERIODREG 0x1c
#define TESTREG 0x20
#define MSGDATA 0x40

struct spi_imx_data
{
    struct device *dev;
    struct completion xfer_done;
    volatile void *base;
    struct spi_master *master;
    struct pinctrl *pinctrl;
    struct pinctrl_state *pinctrl_default;
	struct pinctrl_state *pinctrl_gpio;
};

void imx_spi_io_init(struct spi_imx_data *spi)
{
    int ret;

    spi->pinctrl = devm_pinctrl_get(spi->dev);
    if (!spi->pinctrl)
    {

    }

    spi->pinctrl_default = pinctrl_lookup_state(spi->pinctrl, "default");
    if (!spi->pinctrl_default)
    {
    }

    ret = pinctrl_select_state(spi->pinctrl, spi->pinctrl_default);
    if (ret < 0)
    {

    }
}

void imx_spi_reg_init(struct spi_imx_data *spi)
{
    unsigned int val;

    /* 配置CONREG寄存器
     * bit0 : 		1 	使能ECSPI
     * bit3 : 		1	当向TXFIFO写入数据以后立即开启SPI突发。
     * bit[7:4] : 	0001 SPI通道0主模式，根据实际情况选择，
     *            	   	开发板上的ICM-20608接在SS0上，所以设置通道0为主模式
     * bit[19:18]:	00 	选中通道0
     * bit[31:20]:	0x7	突发长度为8个bit。
     */
    writel((1 << 0) | (1 << 3) | (0xf << 4) | (7 << 20), spi->base + CONREG);

    /*
     * ECSPI通道0设置,即设置CONFIGREG寄存器
     * bit0:	0 通道0 PHA为0
     * bit4:	0 通道0 SCLK高电平有效
     * bit8: 	0 通道0片选信号 当SMC为1的时候此位无效
     * bit12：	0 通道0 POL为0
     * bit16：	0 通道0 数据线空闲时高电平
     * bit20:	0 通道0 时钟线空闲时低电平
     */
    writel(0x0, spi->base + CONFIGREG);

    /*
     * ECSPI通道0设置，设置采样周期
     * bit[14:0] :	0x2000  采样等待周期，比如当SPI时钟为10MHz的时候
     *  		    0x2000就等于1/10000 * 0x2000 = 0.8192ms，也就是连续
     *          	读取数据的时候每次之间间隔0.8ms
     * bit15	 :  0  采样时钟源为SPI CLK
     * bit[21:16]:  0  片选延时，可设置为0~63
     */
    writel(0x2000, spi->base + PERIODREG);

    /*
     * ECSPI的SPI时钟配置，SPI的时钟源来源于pll3_sw_clk/8=480/8=60MHz
     * 通过设置CONREG寄存器的PER_DIVIDER(bit[11:8])和POST_DIVEDER(bit[15:12])来
     * 对SPI时钟源分频，获取到我们想要的SPI时钟：
     * SPI CLK = (SourceCLK / PER_DIVIDER) / (2^POST_DIVEDER)
     * 比如我们现在要设置SPI时钟为6MHz，那么PER_DIVEIDER和POST_DEIVIDER设置如下：
     * PER_DIVIDER = 0X9。
     * POST_DIVIDER = 0X0。
     * SPI CLK = 60000000/(0X9 + 1) = 60000000=6MHz
     */
    val = readl(spi->base + CONREG);
    val &= ~((0xf << 12) | (0xf << 8)); /* 清除PER_DIVDER和POST_DIVEDER以前的设置 */
    val |= (0x9 << 12);
    writel(val, spi->base + CONREG);
}

static void imx_spi_config(struct spi_imx_data *spi, struct spi_device *spi_dev)
{
    imx_spi_reg_init(spi);    
}

static void imx_spi_chipselect(struct spi_device *spi, int is_active)
{
    unsigned int val;
    struct spi_imx_data *ecspi = spi_master_get_devdata(spi->master);

    val = readl(ecspi->base + CONREG);
    if (is_active)
        val &= ~(3 << 18);
    else
        val |= (1 << 18);

    writel(val, ecspi->base + CONREG);
}

static int imx_spi_setup(struct spi_device *spi)
{
    struct spi_imx_data *ecspi = spi_master_get_devdata(spi->master);

    imx_spi_io_init(ecspi);
    imx_spi_config(ecspi, spi);
    
    spi->master->set_cs(spi, 0);

    return 0;
}

static int imx_spi_transfer8(struct spi_imx_data *ecspi, struct spi_transfer *xf)
{
    int len;
    char *rx, *tx;
    
    rx = xf->rx_buf;
    tx = xf->tx_buf;
    len = xf->len;

    while (len--)
    {
        unsigned int tmp = 0;
        while ((readl(ecspi->base + STATREG) & (1 << 0)) == 0)
            ; 
        writel(tx ? *tx++ : 0, ecspi->base + TXDATA);
        
        while ((readl(ecspi->base + STATREG) & (1 << 3)) == 0)
            ;
        tmp = readl(ecspi->base + RXDATA);
        if (rx)
            *rx++ = tmp;
    }
    return xf->len;
}

static int imx_spi_transfer(struct spi_device *spi_dev, struct spi_message *message)
{
    int ret;
    struct list_head *list;
    struct spi_transfer *xf;
    struct spi_imx_data *ecspi = spi_master_get_devdata(spi_dev->master);

    if (list_empty(&message->transfers))
        return 0;

    list_for_each(list, &message->transfers)
    {
        xf = list_entry(list, struct spi_transfer, list);
        ret = imx_spi_transfer8(ecspi, xf);
        if (ret < 0)
            break;
    }

    return ret;
}

static int imx_spi_controler_probe(struct platform_device *pdev)
{
    int ret;
    struct spi_master *master;
    struct spi_imx_data *ecspi;
    struct resource *res;
    
    master = spi_alloc_master(&pdev->dev, sizeof(struct spi_imx_data));
	if (!master)
		return -ENOMEM;

    ecspi = spi_master_get_devdata(master); 

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    ecspi->base = ioremap(res->start, resource_size(res));
    ecspi->master = master;
    ecspi->dev = &pdev->dev;

    master->num_chipselect = 4;
    master->dev.of_node = pdev->dev.of_node;
    
    master->setup = imx_spi_setup;
    master->transfer = imx_spi_transfer;
    master->set_cs = imx_spi_chipselect;

    //platform_set_drvdata(pdev, master);

    ret = spi_register_master(master);

    return ret;
}

struct of_device_id imx_spi_dt_ids[] = 
{
    { .compatible = "fsl,imx6ul-ecspi", .data = NULL },
    { /* sentinel */ }
};

static struct platform_driver imx_spi_driver = 
{
    .driver = 
    {
        .name = "imx6ul-spi",
        .of_match_table = imx_spi_dt_ids,
    },
    .probe = imx_spi_controler_probe,
};

static int __init imx_spi_init(void)
{
    return platform_driver_register(&imx_spi_driver);
}

module_init(imx_spi_init);
